Я хотел бы представить абстрактную перспективу высокого уровня.
Параллельность и одновременность
Операции ввода-вывода взаимодействуют со средой. Среда не является частью вашей программы и не находится под вашим контролем. Среда действительно существует "одновременно" с вашей программой. Как и в случае с другими вещами, вопросы о «текущем состоянии» не имеют смысла: не существует понятия «одновременности» между одновременными событиями. Многие свойства состояния просто не существуют одновременно.
Позвольте мне уточнить это: предположим, вы хотите спросить: «У вас есть больше данных». Вы можете запросить это у параллельного контейнера или вашей системы ввода-вывода. Но ответ, как правило, бездействующий и, следовательно, бессмысленный. Так что, если контейнер говорит «да» - ndash; к тому времени, когда вы попытаетесь прочитать, у него больше не будет данных. Точно так же, если ответ «нет», к тому времени, когда вы попытаетесь прочитать, данные, возможно, уже поступили. Вывод заключается в том, что просто не является таким свойством, как «У меня есть данные», поскольку вы не можете действовать осмысленно в ответ на любой возможный ответ. (Ситуация несколько лучше с буферизованным вводом, где вы можете получить «да, у меня есть данные», что является своего рода гарантией, но вам все равно придется иметь дело с противоположным случаем. И с выводом ситуации все так же плохо, как я описал: вы никогда не знаете, заполнен ли этот диск или сетевой буфер.)
Таким образом, мы приходим к выводу, что невозможно и на самом деле разумно спросить систему ввода-вывода, сможет ли она выполнить операцию ввода-вывода. Единственный возможный способ взаимодействия с ним (так же, как с параллельным контейнером) - это попытка операции и проверка ее успешности или неудачи. В тот момент, когда вы взаимодействуете со средой, тогда и только тогда вы можете узнать, действительно ли взаимодействие было возможно, и в этот момент вы должны посвятить себя выполнению взаимодействия. (Это «точка синхронизации», если хотите.)
EOF
Теперь мы добрались до EOF. EOF - это ответ , полученный при попытке операции ввода / вывода. Это означает, что вы пытались что-то прочитать или записать, но при этом вам не удалось прочитать или записать какие-либо данные, и вместо этого был обнаружен конец ввода или вывода. Это верно практически для всех API ввода-вывода, будь то стандартная библиотека C, iostreams C ++ или другие библиотеки. Пока операции ввода-вывода завершаются успешно, вы просто не можете знать , будут ли дальнейшие операции успешными. Вы должны всегда сначала пытаться выполнить операцию, а затем реагировать на успех или неудачу.
Примеры
В каждом из примеров обратите внимание на то, что мы сначала предпринимаем попытку операции ввода-вывода, а , а затем потребляют результат, если он действителен. Отметим далее, что мы всегда должны использовать результат операции ввода-вывода, хотя результат принимает разные формы и формы в каждом примере.
C stdio, чтение из файла:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
Результат, который мы должны использовать, равен n
, числу прочитанных элементов (которое может быть равным нулю).
C stdio, scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
Результат, который мы должны использовать, это возвращаемое значение scanf
, количество преобразованных элементов.
C ++, извлечение в формате iostreams:
for (int n; std::cin >> n; ) {
consume(n);
}
Результат, который мы должны использовать, - это std::cin
, который можно оценить в логическом контексте и сообщить нам, находится ли поток в состоянии good()
.
C ++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
Результат, который мы должны использовать, снова std::cin
, как и прежде.
POSIX, write(2)
для очистки буфера:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }
Результат, который мы здесь используем, равен k
, числу записанных байтов. Дело в том, что мы можем знать только, сколько байтов было записано после операции записи.
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
Результат, который мы должны использовать: nbytes
, число байтов до и включая символ новой строки (или EOF, если файл не заканчивался символом новой строки).
Обратите внимание, что функция явно возвращает -1
(а не EOF!), Когда возникает ошибка или она достигает EOF.
Вы можете заметить, что мы очень редко произносим слово "EOF". Обычно мы обнаруживаем состояние ошибки другим способом, который нам более интересен (например, невозможность выполнить столько операций ввода-вывода, сколько мы хотели). В каждом примере есть некоторая функция API, которая может явно сообщить нам, что с состоянием EOF было обнаружено, но на самом деле это не очень полезная часть информации. Это гораздо больше деталей, чем мы часто заботимся. Важно то, был ли ввод / вывод успешным, в большей степени, чем как он провалился.
Последний пример, который фактически запрашивает состояние EOF: предположим, у вас есть строка и вы хотите проверить, что она представляет целое число полностью, без дополнительных битов в конце, кроме пробелов. Используя C ++ iostreams, это выглядит так:
std::string input = " 123 "; // example
std::istringstream iss(input);
int value;
if (iss >> value >> std::ws && iss.get() == EOF) {
consume(value);
} else {
// error, "input" is not parsable as an integer
}
Мы используем два результата здесь. Первый - это iss
, сам объект потока, для проверки успешности форматированного извлечения в value
. Но затем, после использования пустого пространства, мы выполняем еще одну операцию ввода-вывода, iss.get()
, и ожидаем, что она завершится с ошибкой как EOF, что является случаем, если вся строка уже была использована форматированным извлечением.
В стандартной библиотеке C вы можете добиться чего-то похожего с функциями strto*l
, проверив, что указатель конца достиг конца входной строки.
Ответ
while(!eof)
неправильно, потому что он проверяет что-то, что не имеет отношения к делу и не может проверить то, что вам нужно знать. В результате вы ошибочно выполняете код, который предполагает, что он обращается к данным, которые были успешно прочитаны, хотя на самом деле этого никогда не происходило.